Googleスプレッドシート(GAS)からGoogle Cloudへアクセスする3つの方法(サンプルコード付き)
Googleスプレッドシートの内容をGoogle Cloudへ、より具体的にはCloud StorageへCSVフォーマットで自動的に保存したい、と思われたことはありませんか? ぼくはあります(ここまで挨拶
その際、慎重に検討すべきは認証方法です。セキュリティ事案に直結するからです。
また運用面・メンテナンス性を考えると、記述しなくてはならないApps Script(GAS)のコードの量は、可読性とのバランスを取った上で少ない方が理想です。外部ライブラリへの依存も、なくて済むならないほうが好ましいでしょう。
Googleの提供する方法としてはこちらのドキュメントに記載されていますが、上述の目的に限定した場合、もろもろの条件込みで、以下の3つの方式に集約されるのかなと考えています。[1]
- GASの「Google Cloudプロジェクト」をCloud Storageと同じプロジェクトにした上でOAuth2認証
- GASの「Google Cloudプロジェクト」をデフォルトのままでOAuth2認証
- サービスアカウントキーを使った認証
これらには一長一短があって、セキュリティ面や、自動化する場合に使いやすいかどうかで星取り表が作れます。
項目 | パターン1 | パターン2 | パターン3 |
---|---|---|---|
GASコード量 | 小 | 多 | 多 |
GAS外部ライブラリ | 不要 | 要 | 不要 |
GASとGoogle Cloudの所属組織 | 同じである必要あり | 別組織可能 | 別組織可能 |
認証方法 | OAuth2 | OAuth2 | 固定キー + JWT |
使用されるプリンシパル | Googleアカウント | Googleアカウント | Google Cloud サービスアカウント |
自動化する場合の適正 | 低 | 低 | 高 |
セキュリティ上の懸念 | 低 | 中 | 高 |
処理の複雑さ | 低 | 高 | 中 |
以下それぞれのパターンごとに、特徴と向いているユースケース、サンプルコード、懸念される点と緩和方法のアイディアについて記述します。
その前に: 「自動化する場合の適正」について
これは、GASが動作する際にどの認証情報が使われるか に依存しています。
GAS、つまり Google Apps スクリプトが動作するタイミングは、大きく「手動実行」と「トリガーによる自動実行」のふたつがあると思います。
そのトリガーにも種類がありますから、今回のような「スプシの内容をCloud Storageに置きたい」というユースケースを考えると、ざっくり以下の3パターンになるのではと思います。
- 手動(誰かが明示的に実行を指示すること)で実行する
- スプシの編集(内容の変更など)をトリガーに実行する
- 時限式のトリガーで定期的(1日1回など)に実行する
このうち上2つは、「その操作を行ったGoogleアカウント」によってGASが起動され、その操作者のGoogleアカウントの権限でGASが動作します。
これはつまり、「操作する可能性のあるひと全員のGoogleアカウントをGoogle Cloudのプリンシパルとして登録する必要がある」ということで、自動化の際にはこれがネックになります。
特にスプシの編集権限範囲と、同期する(=Google Cloudに書き込む)ことの権限範囲は、ガバナンス上は同じにする必要がない(むしろ分けたい)ケースが多いと思うので、悩ましくなると思います。
「時限式トリガー」の場合は、そのトリガーを設定したひと(オーナー)のGoogleアカウントが毎回使われることになります。ある程度限定されることになるので前2つよりは多少マシですが、属人性の意味ではこちらも好ましいとは言えないでしょう。
「自動化する場合の適正」は、これらを考慮して採点しています。
それでは実際に、各パターンについて細かく見ていきます。
パターン 1: GASの「Google Cloudプロジェクト」をCloud Storageと同じにする
Apps スクリプトの設定「Google Cloud Platform(GCP)プロジェクト」において、Cloud Storageの存在するGoogle Cloudプロジェクトを指定する方法です。
Googleスプレッドシートを含むGoogle Workspace(GWS)の組織と、CSVを置きたいCloud Storageが存在するGoogle Cloudプロジェクトの所属する組織が同じ場合は、おそらくこの方法が一番コードも少なくシンプルだと思います。
また、GASの動作ログも同時にGoogle CloudプロジェクトのCloud Loggingに送信されることになるため、管理もより強固になります。
認証方法もOAuth2になりセキュリティ上も推奨されるため、個人的にはまず一番最初に採用を検討するべき方式だと思います。
一方で、ネックは前述した「GWSとGoogle Cloudが同じ組織」になります。
同じ企業内でも(経理上の理由などで)あえて組織を分けるケースもあると思いますので、利用したい環境がどうなっているかは確認する必要があるでしょう。
構築するには
概ね以下の順番になるかと思います。
- Cloud StorageのあるGoogle Cloudプロジェクト上に、OAuth同意画面・クライアントを設定する
- ↑のプロジェクト上に操作者のGoogleアカウントをプリンシパルとして追加、Cloud Storageに対する書き込み権限を追加する
- GAS の「プロジェクトの設定」にて、Google Cloudプロジェクトを↑のプロジェクトに変更する(プロジェクトIDではなくプロジェクト番号で指定)
- 後述するコードをGASに設定、カスタマイズする
- 何かしらのトリガーにて 4. のコードを実行するよう設定する
1.〜3. の手順については、下記ドキュメントもご参照下さい。
また 1. については、後述するパターン 2 で紹介しているブログにも一部同様の手順がありますので、そちらも参考になるかと思います。
GASコード
ある特定のシートの内容をCSVにしてCloud Storageにアップロードする、という目的のために必要なGASのコードは、下記の様になります。
// パターン1
function main_uploadToGCS_1() {
var sheetName = 'シート1'; // CSV化する対象のシート名
var bucketName = 'XXXXXXXX'; // CSVをアップロードするGCSバケット名
var objectName = 'exported_data.csv'; // アップロードする際のオブジェクト名
// スプレッドシートの内容を取得
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
// 取得した内容をCSV形式に変換
var csv = values.join("\n");
var blob = Utilities.newBlob(csv, 'text/csv', objectName);
// 認証のためのトークン取得
var accessToken = ScriptApp.getOAuthToken();
// アップロード準備
var url = 'https://storage.googleapis.com/upload/storage/v1/b/' + bucketName + '/o?uploadType=media&name=' + objectName;
var options = {
method: 'post',
contentType: 'text/csv',
payload: blob.getBytes(),
headers: {
Authorization: 'Bearer ' + accessToken,
}
};
// Cloud Storageにアップロード
try {
var response = UrlFetchApp.fetch(url, options);
Logger.log('File uploaded successfully: ' + response.getContentText());
} catch (e) {
Logger.log('Error uploading file: ' + e.toString());
}
}
コメントいれて36行程度。この程度であれば見通しもよくて、何をやっているのかも分かりやすいコードではないでしょうか。
このパターンでの認証部分は 16行目の ScriptApp.getOAuthToken()
のみ。何かしらのクレデンシャル情報も必要としないため、一番セキュアな方式と言えると思います。
前述したGWSとGoogle Cloudの組織の件が問題にならない場合は、まずこちらを検討して良いかと思います。
その際には、「自動化する場合の適正」で話した内容が障害になるかどうかをご確認下さい。
パターン 2: 別組織間で OAuth2 認証
Apps スクリプトの設定「Google Cloud Platform(GCP)プロジェクト」設定を、デフォルトのままでOAuth認証する方法です。
以下の弊社ブログで説明されている方法になります。
構築するには
- Cloud StorageのあるGoogle Cloudプロジェクト上に、OAuth同意画面・クライアントを設定する
- ↑のプロジェクト上に操作者のGoogleアカウントをプリンシパルとして追加、Cloud Storageに対する書き込み権限を追加する
- GASで使用するライブラリとして
1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
(OAuth2)を追加する - 後述するコードをGASに設定、カスタマイズする
- 何かしらのトリガーにて 4. のコードを実行するよう設定する
パターン 2と比較すると、手順 3. が変更になっただけですね。また、手順 1. で作成したOAuth2クライアントのクライアントIDとシークレットを、手順 4. でコードに埋め込んでください。
ちなみに 3. で追加したOAuth2ライブラリのコードはこちらです。
細かい手順は、前述した Google のヘルプドキュメントや、上に貼ったブログ記事を参考にして下さい。
下記に述べるコード内の「認証コールバック」なども、ブログ記事に準拠しています。
GASコード
問題のGASコードはこちらになります。1,000行近く。パターン 1 と比べて、一挙に3倍くらいになりました。
増えた部分は単純に認証部分です。外部ライブラリを使ってこれなので、結構な分量が増えているのが分かって頂けるかと。。
ちなみに、上記星取り表で「セキュリティ上の懸念」を「中」としたのは、Oauth2クライアントのIDとシークレットを指定しなければならないからです。
そんな情報をこのコードではハードコードしてしまっていますが、これはあくまで PoC ということで、実際に使う場合はこれらのクレデンシャル情報をApps Scriptのスクリプトプロパティ機能をつかって格納するなどの工夫をお願いします。
// OAuth2クライアントのクレデンシャル情報
var CLIENT_ID = "xxxxxxxxxxxxxxxxxxxxxx";
var CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxx";
// 認証用サービス取得
function getStorageService(cfg) {
return OAuth2.createService('provisioning')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getScriptProperties())
.setScope('https://www.googleapis.com/auth/devstorage.full_control')
.setParam('login_hint', Session.getActiveUser().getEmail())
}
// 認証コールバック
function authCallback(request) {
var service = getStorageService();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
Logger.log("isAuthorized!");
return HtmlService.createHtmlOutput('<center>認証完了<br>タブを閉じてください。</center>');
} else {
Logger.log("is not Authorized...");
return HtmlService.createHtmlOutput('認証エラー<br>認証情報をお確かめください。');
}
}
// アクセストークンを取得するための関数
function getAccessToken_2() {
// 認証用サービス取得
var service = OAuth2.createService('provisioning')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(clientId)
.setClientSecret(clientSecret)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getScriptProperties())
.setScope('https://www.googleapis.com/auth/devstorage.full_control')
.setParam('login_hint', Session.getActiveUser().getEmail());
if (!service.hasAccess()) {
// 認証ダイアログ表示
var authorizationUrl = service.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'<center><a href="<?= authorizationUrl ?>" target="_blank">認証実行</a></center>' +
'<br><br><center>GCPプロジェクト認証が必要です。<br>認証実行後にもう一度アップロードしてください。</center>'
);
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
SpreadsheetApp.getUi().showModalDialog(page, "Google API認証");
return false
}
return service.getAccessToken();
}
// main
function main_uploadToGCS_2() {
var sheetName = 'シート1'; // CSV化する対象のシート名
var bucketName = 'XXXXXXXX'; // CSVをアップロードするGCSバケット名
var objectName = 'exported_data.csv'; // アップロードする際のオブジェクト名
// スプレッドシートの内容を取得
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
// 取得した内容をCSV形式に変換
var csv = values.join("\n");
var blob = Utilities.newBlob(csv, 'text/csv', objectName);
// 認証のためのトークン取得
var accessToken = getAccessToken_2();
// アップロード準備
var url = 'https://storage.googleapis.com/upload/storage/v1/b/' + bucketName + '/o?uploadType=media&name=' + objectName;
var options = {
method: 'post',
contentType: 'text/csv',
payload: blob.getBytes(),
headers: {
Authorization: 'Bearer ' + accessToken,
}
};
// Cloud Storageにアップロード
try {
var response = UrlFetchApp.fetch(url, options);
Logger.log('File uploaded successfully: ' + response.getContentText());
} catch (e) {
Logger.log('Error uploading file: ' + e.toString());
}
}
パターン 1・2 自動化適正上の「共通の問題」について
ここまでふたつパターンを説明しましたが、両方に共通しているのはOAuth2認証を使っている部分です。
これは固定化された認証情報を持たず、一時的なクレデンシャルを作って認証を行うためセキュアに構築できる仕組みなのですが、自動化させようとすると問題があります。
つまり、「一時的」なクレデンシャルは短時間、このケースの実測で 1時間しかもちません。1時間おきに再認証が必要になります。
両パターンともGoogleアカウントを使って認証するため、その再認証の操作は人間が行うことになります。これでは自動化はできません。
自動化適性が「低」としたのはそのためです。これらのパターンは手動操作を前提に使うべきかと思います。
例えば以下のようなコード(シンプルトリガー)をGASに書いておくことで、スプレッドシートに「手動アップロード」のためのメニュー項目を追加することができます。
// メニューからの手動アップロード
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('| CSVアップロード')
.addItem('CSV手動アップロード', 'main_uploadToGCS_1')
.addToUi();
}
シンプルトリガーについての詳細は、こちらのドキュメントを参照ください。
パターン 3: サービスアカウントキーを使用する
じゃあ定期的な実行だったり、スプシの編集に応じて実行させる場合にはどうするか、というと、固定的なクレデンシャルによる認証しかありません。具体的にはサービスアカウントキーを使った認証になります。[2]
断っておきますが、サービスアカウントキーを使用した方法はGoogleは推奨していません。
なので、この方法は「他に手がない」となったときに検討し、採用するとなった場合も極力危険性を減らすための工夫をお願いします。
構築するには
パターン 1・2 と違い、OAuth2クライアントの設定は不要です。代わりにサービスアカウントキーの作成が必要になります。
- Cloud StorageのあるGoogle Cloudプロジェクトにサービスアカウントを作成、キーをダウンロードする
- 後述するコードをGASに設定、カスタマイズする
- 何かしらのトリガーにて 2. のコードを実行するよう設定する
手順 1. で作成したサービスアカウントキーの情報を、手順 2. でコードに埋め込んでください。
下記の手順で作成したJSONの client_email
と private_key
が必要になります。
GASコード
この場合のコードはこちらになります。行数的にはパターン 2 とほぼ変わりなしですが、外部ライブラリは必要としません。
JWT(JSON Web Token)を自力で組み立てるコードになってるので、ここを外部ライブラリなどにしてあげれば見通しも良くなるかもです。
一方で、こちらのコードも認証情報がハードコードされています。
パターン 2 と比較にならないくらい直接的な認証情報なので、実運用にあげる時にはこちらも工夫が必要です。方法案については後述します。
// サービスアカウントキー情報
var SA_CLIENT_EMAIL = "xxxxxxxxxxxxxxxxxxxxxx";
var SA_PRIVATE_KEY = "xxxxxxxxxxxxxxxxxxxxxx";
// JWT作成
function createJwt_3() {
var header = {
"alg": "RS256",
"typ": "JWT"
};
var now = Math.floor(Date.now() / 1000);
var claims = {
"iss": SA_CLIENT_EMAIL,
"scope": "https://www.googleapis.com/auth/devstorage.read_write",
"aud": "https://oauth2.googleapis.com/token",
"exp": now + 3600,
"iat": now
};
var stringifiedHeader = Utilities.base64EncodeWebSafe(JSON.stringify(header));
var stringifiedClaims = Utilities.base64EncodeWebSafe(JSON.stringify(claims));
var toSign = stringifiedHeader + "." + stringifiedClaims;
var signature = Utilities.computeRsaSha256Signature(toSign, SA_PRIVATE_KEY);
var signedJwt = toSign + "." + Utilities.base64EncodeWebSafe(signature);
return signedJwt;
}
// アクセストークンを取得するための関数
function getAccessToken_3() {
// JWT組み立て
var jwt = createJwt_3();
var tokenUrl = "https://oauth2.googleapis.com/token";
var options = {
"method": "post",
"payload": {
"assertion": jwt,
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"
}
};
// アクセストークンの取得
var response = UrlFetchApp.fetch(tokenUrl, options);
var tokenResponse = JSON.parse(response.getContentText());
return tokenResponse.access_token;
}
// main
function main_uploadToGCS_3() {
var sheetName = 'シート1'; // CSV化する対象のシート名
var bucketName = 'XXXXXXXX'; // CSVをアップロードするGCSバケット名
var objectName = 'exported_data.csv'; // アップロードする際のオブジェクト名
// スプレッドシートの内容を取得
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
// 取得した内容をCSV形式に変換
var csv = values.join("\n");
var blob = Utilities.newBlob(csv, 'text/csv', objectName);
// 認証のためのトークン取得
var accessToken = getAccessToken_3();
// パラメータ組み立て
var url = 'https://storage.googleapis.com/upload/storage/v1/b/' + BucketName + '/o?uploadType=media&name=' + CsvName;
var options = {
method: 'post',
contentType: 'text/csv',
payload: csvBlob.getBytes(),
headers: {
Authorization: "Bearer " + accessToken,
}
};
// Cloud Storageにアップロード
try {
var response = UrlFetchApp.fetch(url, options);
Logger.log('File uploaded successfully: ' + response.getContentText());
} catch (e) {
Logger.log('Error uploading file: ' + e.toString());
}
}
セキュリティリスク緩和策について
このパターン 3 はGoogleが「非推奨」と明言しているサービスアカウントキー方式なので、当然リスクについては確認しておく必要があります。
この方式の最も大きなリスクは、「キーの漏洩」とその漏洩したキーによる「リソースの不正利用」でしょう。
この問題の対策としては、ざっくり以下かと思います。
- サービスアカウントに割り当てる権限(ロール)を最小にする
- キー自体へのアクセス経路を最小にする
- キーのローテーションをしやすくする
1. サービスアカウントに割り当てる権限(ロール)を最小にする
こちらは基本中の基本「最小権限の原則」ですね。
今回の場合は「Cloud Storageの特定のバケットに、特定の名前のオブジェクトを保存」できれば良いので、プリンシパル(サービスアカウント)に付与する権限と条件を絞りましょう。例えば以下のようになります:
- 権限: Storage オブジェクト ユーザー(
roles/storage.objectUser
) - 条件:
- 条件タイプ:
名前
- 演算子:
次から始まる
- 値:
projects/_/buckets/<バケット名>/objects/source/<オブジェクト名>
- 条件タイプ:
権限(Role)ですが事前定義ロールを使う場合、一見「Storage オブジェクト作成者(roles/storage.objectCreator
)」でも良さそうですが、この権限では「同じオブジェクト名で 上書き保存が出来ない」ので今回のケースでは使用できません。
毎回オブジェクト名が変わるような実装なら、そちらのほうがいいと思います。またそうでなくても、必要に応じてカスタムロールを検討しても良いかもしれません。
条件(Conditions)でCloud Storageオブジェクトを指定する場合は、Cloud APIのリソース名フォーマットで記載する必要があるのでご注意ください。
例えば、サンプルコードに合わせて gs://XXXXXXXX/exported_data.csv
に対するリソース名は projects/_/buckets/XXXXXXXX/objects/source/exported_data.csv
となります。
Cloud Storageのオブジェクト名にはバージョン識別子を含む場合があるので、演算子は「 次から始まる
(前方一致)」としたほうが無難なのでそうしています。
- パスパターンについて | Eventarc Standard | Google Cloud
- リソース名 | Cloud API Design Guide | Google Cloud
- 推奨事項 - 命名に関する考慮事項 - Cloud Storage オブジェクトについて | Google Cloud
2. キー自体へのアクセス経路を最小にする
キー情報をハードコードしない(分離する)、は基本ですね。
その手段としてよく使われるのは、前述したApps Scriptの「スクリプトプロパティ機能」だと思うんですが、それでもスプレッドシートやGASコードにアクセスできるひとはキーの内容を見ることが出来てしまいます。
ここに一般解はないんですが、今回試してみたのは「キー情報の書かれたファイル(以下キーファイル)をGoogleドライブに置く」という手法です。
これは、時限トリガーで動作する場合は最低限 時限トリガーを設定したひと(Googleアカウント)に対してのみキーファイルの読み込み権限があればいい わけで、このようにすることでスプレッドシート、コードと認証情報を権限的に分離することが可能になるわけです。
また、この方法は後述する「ローテーションしやすくする」にも効いてきます。
3. キーのローテーションをしやすくする
前述の 2. でコードと認証情報を分離でき、かつ認証情報を独立したファイル(オブジェクト)とすることができたため、ここ(ローテーション)を自動化することも可能なほど簡単になっていると思います。
そこからさらに、サービスアカウントのキー情報はJSON形式でのダウンロードが可能なため、それをそのままGASでパースするようにすれば、ますますローテーションも容易になるかなと思いました。
というわけで、この 3. を反映させたパターンのコードを以下に。
パターン 3b: セキュリティリスク緩和版
サービスアカウントキーの作成とダウンロード
パターン 3 ではダウンロードしたキーファイルを手でパースして必要な情報を抜き出したわけですが、今回はダウンロードしたJSONファイルそのままをGoogleドライブに保存します。
ちなみにサービスアカウントキーの作成と認証情報のダウンロードをCLIで行うコマンドラインは、例えばこうなります。[3]
PROJECT_ID="<Google CloudプロジェクトID>"
SA_NAME="<作成するサービスアカウントの名称>"
gcloud iam service-accounts keys create \
${SA_NAME}-$(date +%Y%m%d).json \
--iam-account=${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \
--key-file-type=json
このJSONファイルを、時限式トリガーを設定するひとがアクセス可能な状態でGoogleドライブに置いておきましょう。
そのGoogleドライブ上のURLから、キーファイルのIDが取得出来ます。
URLの、〜〜file/d/
と /view?〜〜
に挟まれた部分がそれになります。
例:
- URL :
https://drive.google.com/file/d/1C76779028DCA4C61ADB63586xEXAMPLE/view?usp=drive_link
- --> ファイルID :
1C76779028DCA4C61ADB63586xEXAMPLE
- --> ファイルID :
GASコード
ほぼパターン 3 と同じですが、関数 getAccessToken_3b()
のあたま5〜6行くらいでGoogleドライブ上のキーファイルを読み込み、そのままJSONオブジェクトとして後続の処理に渡しています。
コード量もそこまで増やさずに実現できました。
const SAKeyFileId = "<サービスアカウントキーファイルのID>";
// JWT作成
function createJwt_3b(serviceAccount) {
var header = {
"alg": "RS256",
"typ": "JWT"
};
var now = Math.floor(Date.now() / 1000);
var claims = {
"iss": serviceAccount.client_email,
"scope": "https://www.googleapis.com/auth/devstorage.read_write",
"aud": "https://oauth2.googleapis.com/token",
"exp": now + 3600,
"iat": now
};
var stringifiedHeader = Utilities.base64EncodeWebSafe(JSON.stringify(header));
var stringifiedClaims = Utilities.base64EncodeWebSafe(JSON.stringify(claims));
var toSign = stringifiedHeader + "." + stringifiedClaims;
var signature = Utilities.computeRsaSha256Signature(toSign, serviceAccount.private_key);
var signedJwt = toSign + "." + Utilities.base64EncodeWebSafe(signature);
return signedJwt;
}
// アクセストークンを取得するための関数
function getAccessToken_3b() {
// スクリプトプロパティからサービスアカウントキーを取得
var file = DriveApp.getFileById(SAKeyFileId);
var jsonStr = file.getBlob().getDataAsString("UTF-8");
var serviceAccount = JSON.parse(jsonStr);
Logger.log("get Service Account key info: " + serviceAccount.client_email)
// JWT組み立て
var jwt = createJwt_3b(serviceAccount);
var tokenUrl = "https://oauth2.googleapis.com/token";
var options = {
"method": "post",
"payload": {
"assertion": jwt,
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"
}
};
// アクセストークンの取得
var response = UrlFetchApp.fetch(tokenUrl, options);
var tokenResponse = JSON.parse(response.getContentText());
return tokenResponse.access_token;
}
// main
function main_uploadToGCS_3b() {
var sheetName = 'シート1'; // CSV化する対象のシート名
var bucketName = 'XXXXXXXX'; // CSVをアップロードするGCSバケット名
var objectName = 'exported_data.csv'; // アップロードする際のオブジェクト名
// スプレッドシートの内容を取得
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
// 取得した内容をCSV形式に変換
var csv = values.join("\n");
var blob = Utilities.newBlob(csv, 'text/csv', objectName);
// 認証のためのトークン取得
var accessToken = getAccessToken_3b();
// パラメータ組み立て
var url = 'https://storage.googleapis.com/upload/storage/v1/b/' + BucketName + '/o?uploadType=media&name=' + CsvName;
var options = {
method: 'post',
contentType: 'text/csv',
payload: csvBlob.getBytes(),
headers: {
Authorization: "Bearer " + accessToken,
}
};
// Cloud Storageにアップロード
try {
var response = UrlFetchApp.fetch(url, options);
Logger.log('File uploaded successfully: ' + response.getContentText());
} catch (e) {
Logger.log('Error uploading file: ' + e.toString());
}
}
まとめ
GAS (Google Workspace) と Cloud Storage (Google Cloud) の連携ですが、いざやろうとするとまとまった情報がなかったり、いろんなパターンが考えられるなど、なかなか一筋縄に行きませんでした。
数ヶ月ほど生成AIへの相談と試行錯誤を繰り返したあと、個人的におちついたのがパターン 3b です。
状況によって最適解も変わってくると思いますが、何かしら参考になれば幸いです。
個人の感想です ↩︎
GWS相手でもWorkload Identity連携が組めればいいんですが。。。 ↩︎
一度作成されたサービスアカウントキーの秘密鍵情報を、あとからダウンロードすることはできません ↩︎